#include "shellsort.h"
#include <QPainterPath>
#include <QScopeGuard>

ShellSort::ShellSort(QObject *parent) : SortBase{parent}
{
    animation.setStartValue(0.0);
    animation.setEndValue(1.0);
    animation.setEasingCurve(QEasingCurve::OutQuart);
    animation.setLoopCount(1);
    connect(&animation, &QVariantAnimation::finished, &loop, &QEventLoop::quit);
    connect(&animation, &QVariantAnimation::valueChanged, this, &SortBase::updateRequest);
}

void ShellSort::sort(size_t num_of_elem, int interval)
{
    // ensure calling setRunState(false) and update at the end of function
    auto guard = qScopeGuard([this] {
        setRunState(false);
        emit updateRequest();
    });

    stop();
    if (arr.size() != num_of_elem)
        randomArray(num_of_elem);
    setRunState(true);

    this->interval = interval;

    int &i = curr_i, &j = curr_j, &d = sep_gap;
    for (d = (int) num_of_elem >> 1; d > 0; d >>= 1) {
        for (i = sep_gap; i < (int) num_of_elem; i++) {
            j = 0x3FFFFFFF;
            wait(this->interval);
            emit updateRequest();
            if (!runState()) {
                return;
            }

            if (arr[i] < arr[i - d]) {
                for (j = i - d; j >= 0 && arr[j] > arr[j + d]; j -= d) {
                    swapFlag = true;
                    wait(this->interval * 3);

                    if (runState()) {
                        std::swap(arr[j + d], arr[j]);
                        swapFlag = false;
                    }

                    emit updateRequest();
                    if (!runState())
                        return;
                }
            }
        }

        if (!runState()) {
            return;
        }
    }
}

void ShellSort::stop()
{
    setRunState(false);
    animation.stop();
    loop.quit();
    emit updateRequest();
}

void ShellSort::draw(QPainter *painter, int width, int height)
{
    painter->setPen(QColor(200, 200, 200));
    const size_t len = arr.size();
    const size_t max_elem = *(std::max_element(arr.begin(), arr.end()));

    // border
    constexpr int left_space = 0;
    constexpr int right_space = 0;
    constexpr int top_space = 20;
    constexpr int bottom_space = 60;

    // space width between two column
    constexpr int item_space = 10;

    const int text_height = painter->fontMetrics().height();

    // distance between column and number text
    constexpr int text_space = 15;

    // item width is dynamically adjusted according to the window width
    const double item_width = (width + item_space - left_space - right_space) / (double) len
                              - item_space;
    const double item_bottom = height - bottom_space;

    // item height is also dynamically adjusted according to the window height
    const double height_factor = (height - top_space - bottom_space) / (double) max_elem;

    // current drawing column width and height
    double item_left = 0;
    double item_height = 0;

    QColor color;
    for (int i = 0; i < (int) len; i++) {
        // position of the column
        item_left = left_space + i * (item_width + item_space);
        item_height = height_factor * arr.at(i);

        // color of the column
        color = QColor(200, 200, 200);

        // current comparing column
        if (runState()) {
            int offset = animation.currentValue().toDouble() * sep_gap * (item_width + item_space);

            if (i <= curr_i && i % sep_gap == curr_i % sep_gap) {
                color = QColor(0, 170, 0);
            }

            if (i == curr_j && curr_j >= 0) {
                color = QColor(0, 170, 255);
                if (swapFlag) {
                    item_left += offset;
                }
            } else if (i == curr_j + sep_gap) {
                color = QColor(0, 170, 255);
                if (swapFlag) {
                    item_left -= offset;
                }
            }
        }

        // draw text
        painter->drawText(item_left,
                          item_bottom + text_height + text_space,
                          QString::number(arr.at(i)));

        // draw rect
        painter->fillRect(item_left, item_bottom - item_height, item_width, item_height, color);
    }

    if (runState()) {
        painter->setPen(QPen(QColor(255, 170, 0), 2));
        int y_top = height - 20;
        int y_bottom = height - 8;
        int first_i = curr_i % sep_gap;
        int last_i = 0;
        for (int i = 0; i < (int) len; i++) {
            item_left = left_space + i * (item_width + item_space) + item_width / 2;
            if (i % sep_gap == curr_i % sep_gap) {
                last_i = i;
                painter->drawLine(item_left, y_top, item_left, y_bottom);
            }
        }
        int left = left_space + first_i * (item_width + item_space) + item_width / 2;
        int right = left_space + last_i * (item_width + item_space) + item_width / 2;
        painter->drawLine(left, y_bottom, right, y_bottom);

        item_left = left_space + curr_i * (item_width + item_space);
        const double loop_top = height - 24;
        QPainterPath m_path;
        m_path.moveTo(item_left + item_width / 2.0, loop_top);
        m_path.lineTo(item_left, loop_top + item_width / 2.0);
        m_path.lineTo(item_left + item_width, loop_top + item_width / 2.0);
        m_path.lineTo(item_left + item_width / 2.0, loop_top);
        painter->fillPath(m_path, QColor(0, 255, 0));
    }
}

void ShellSort::skip()
{
    this->interval = 1;
}

void ShellSort::wait(int ms)
{
    animation.setDuration(ms);
    animation.start();
    loop.exec();
}
